package com.tool.genxml;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.tool.genxml.entity.XRow;
import com.tool.genxml.entity.XSheet;
import com.tool.genxml.handler.MsgSheetHandler;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import static com.tool.genxml.constant.SysConstant.DOCUMENT;

/**
 * 产生Xml的方法类
 * @author zhoubin
 */
public class XmlGen {

    private MsgSheetHandler handler;

    /**
     * 生成的数据库配置数据
     */
    private XSheet xSheet;

    /**
     * 生成的document对象
     */
    private Document document;

    /**
     * 当前sheet页总行数
     */
    private int sheetNum;

    public XmlGen(MsgSheetHandler handler) {
        this.handler = handler;
        this.xSheet = new XSheet(handler.sheetName());
        this.document = DocumentHelper.createDocument();
        this.sheetNum = handler.rowNum();
    }

    /**
     * 根据原Excel中的数据转化为xml
     */
    public void parse() {
        // 创建跟节点
        Element docEle = this.document.addElement(DOCUMENT);

        XRow row = handler.getRowByIndex(0);
        String tag = row.getTag();
        Element document = docEle.addElement(tag);

        XRow xRow = new XRow();
        createXml(document, xRow);

        System.out.println("解析" + handler.sheetName() + "sheet页成功");
    }

    /**
     * 生成xml，并生成配置信息
     *
     * @param preElement 上一个节点的element
     * @param preRow 上一个节点配置行信息
     * @return 返回当前节点
     */
    public XRow createXml(Element preElement, XRow preRow) {

        // 获取上一个节点的行号
        int index = preRow.getIndex();

        // 当前一行为最后一行时
        // 因为下面需要加1，所以上面的那行已经是最后一行了
        if (index >= sheetNum - 1) {
            preElement.setText(preRow.getValue());

            // 向Excel中添加一条记录
            preRow.setXPath(preElement.getUniquePath());
            xSheet.addRow(preRow);
            return null;
        }

        XRow xRow = handler.getRowByIndex(index + 1);
        // 转换为一个新的对象
        XRow el = BeanUtil.toBean(xRow, XRow.class);
        el.setIndex(index + 1);

        // 接收递归返回来的节点
        XRow retEl = el;

        // 如果当前节点的等级大于上一个节点，说明当前节点是上一个节点的子节点
        if (el.getLevel() > preRow.getLevel()) {
            // 直接将本节点加入到上一个节点之后
            Element thisEl = preElement.addElement(el.getTag());

            // 如果当前节点为循环域，则需要添加一条数据
            if (el.isCyclic()) {
                XRow newRow = BeanUtil.toBean(el, XRow.class);
                newRow.setXPath(thisEl.getUniquePath());
                // 当循环域套循环域是，则当前节点的父节点先设为外层循环域的节点
                if (preRow.isCyclic() || CharSequenceUtil.isNotBlank(preRow.getParentPath())) {
                    String newRowParentPath = preRow.isCyclic() ? preElement.getUniquePath() : preRow.getParentPath();
                    newRow.setParentPath(newRowParentPath);
                    el.setParentPath(newRowParentPath);
                }
                xSheet.addRow(newRow);
            } else {
                // 如果当前节点不是循环域，则判断其外层节点是否有循环域的情况
                // 判断父节点是否为循环节点，如果是循环节点，则向其父节点中填值
                if (preRow.isCyclic()) {
                    el.setParentPath(preElement.getUniquePath());
                } else if (CharSequenceUtil.isNotBlank(preRow.getParentPath())) {
                    // 判断外层节点是否有循环节点，如果是循环节点，则上一个节点的父节点就是外层循环节点的xpath
                    el.setParentPath(preRow.getParentPath());
                }
            }

            // 继续循环下一行，当前返回值是当前节点下的最后一个子节点的下一行。
            retEl = createXml(thisEl, el);

            // 循环值返回来的那个节点跟当前节点不是同级
            while (retEl != null && retEl.getLevel() == el.getLevel()) {
                // 注：确保在下一个循环时拿到的还是这个节点，需要将行号减一
                el.setIndex(retEl.getIndex() - 1);
                retEl = createXml(thisEl, el);
            }
        } else if (el.getLevel() == preRow.getLevel()) {

            // 兄弟节点如果有父节点，则表示其父节点含有循环域
            // 先判断同级节点的父节点是否不为空之后再在下一步给同级的上一个节点的父节点赋值的好处，
            // 这样上一个同级节点不会传递到下一个节点去
            if (CharSequenceUtil.isNotBlank(preRow.getParentPath())) {
                el.setParentPath(preRow.getParentPath());
            }

            // 如果当前节点的等级与上一个节点的等级相同，说明当前节点与上一节点为兄弟节点，
            // 如果上一个节点下没有子节点，则在上一个节点中设置值
            if (!preElement.hasContent()) {
                preElement.addText(preRow.getValue());

                // 因为前一个节点没有子节点，所以如果前一个节点是循环域的话，就已经复制过一条数据了，preRow仅仅只表示循环域下的一条数据
                if (preRow.isCyclic()) {
                    preRow.setParentPath(preElement.getUniquePath());
                    preRow.setCyclic(false);
                }

                // 向配置文件Excel中添加一条配置记录
                preRow.setXPath(preElement.getUniquePath());
                xSheet.addRow(preRow);
            }

            // 获取上一个节点的父节点
            Element parent = preElement.getParent();
            // 将当前节点存入到父节点中
            Element thisEl = parent.addElement(el.getTag());

            // 如果当前为循环域，则需要添加一条数据
            if (el.isCyclic()) {
                XRow newRow = BeanUtil.toBean(el, XRow.class);
                newRow.setXPath(thisEl.getUniquePath());
                xSheet.addRow(newRow);
            }

            retEl = createXml(thisEl, el);
        } else {
            // 如果当前节点的等级比上一个节点的等级小，说明当前节点可能与上一个节点的父节点平级，
            // 且上一个节点一定为叶子节点，为上一个节点赋值后返回
            preElement.addText(preRow.getValue());

            // 因为前一个节点没有子节点，所以如果前一个节点是循环域的话，就已经复制过一条数据了，preRow仅仅只表示循环域下的一条数据
            if (preRow.isCyclic() && CharSequenceUtil.isNotBlank(preRow.getParentPath())) {
                preRow.setParentPath(preElement.getUniquePath());
                preRow.setCyclic(false);
            }

            // 向Excel配置表中添加记录
            preRow.setXPath(preElement.getUniquePath());
            xSheet.addRow(preRow);
        }

        return retEl;
    }

    /**
     * 生成xml文件
     * @param path
     */
    public void createXmlFile(String path) throws IOException {
        XMLWriter writer = null;
        try {
            // 5、设置生成xml的格式
            OutputFormat format = OutputFormat.createPrettyPrint();
            // 设置编码格式
            format.setEncoding("UTF-8");
            // 6、生成xml文件
            File file = new File(path);
            writer = new XMLWriter(new FileOutputStream(file), format);
            // 设置是否转义，默认使用转义字符
            writer.setEscapeText(false);
            writer.write(document);
        } catch (IOException e) {
            System.err.println("生成" + handler.sheetName() + "xml文件失败。");
            throw e;
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    System.err.println("生成" + handler.sheetName() + "xml文件后关闭资源失败。");
                }
            }
        }
    }

    /**
     * 将当前生成的配置列表存入Excel中
     * @param outWorkBook
     */
    public void addSheet(XSSFWorkbook outWorkBook) {
        xSheet.createSheet(outWorkBook);
    }
}
